-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local client = require 'pcie_device.client'
local skynet = require 'skynet'
local class = require 'mc.class'
local service = require 'pcie_device.service'
local object_manage = require 'mc.mdb.object_manage'
local reboot_manage = require 'mc.mdb.micro_component.reboot'
local orm_object_manage = require 'mc.orm.object_manage'
local c_device_service = require 'device.device_service'
local c_biz_topo_service = require 'biz_topo.biz_topo_service'
local c_service_manager = require 'service_manager'
local app_ipmi = require 'pcie_device.ipmi.ipmi'
local log = require 'mc.logging'
local intf_debug = require 'mc.mdb.micro_component.debug'
local msg = require 'pcie_device.ipmi.ipmi_message'
local ipmi = require 'ipmi'
local cc = ipmi.types.Cc
local mdb = require 'mc.mdb'
local mdb_service = require 'mc.mdb.mdb_service'
local get_non_virtual_interface_objects = mdb_service.get_non_virtual_interface_objects
local def = require 'biz_topo.def'
local mdb_config_manage = require 'mc.mdb.micro_component.config_manage'
local device_mgmt = require 'device_mgmt.device_mgmt'
local common_interface = require 'common_interface'
local config_manage = require 'config_manage.config_manage'
local bs = require 'mc.bitstring'

local MANUFACTURE_ID<const> = 0x0007db

local pcie_device_app = class(service)

function pcie_device_app:ctor()
    self.service_manager = c_service_manager.new()
    self.device_service = c_device_service.new(self.bus, self.db, self.reset_local_db, self.service_manager)
    self.biz_topo_service = c_biz_topo_service.new(self.bus, self.service_manager, self.db, self.reset_local_db)
    self.common = common_interface.new(self.bus)
end

function pcie_device_app:check_dependencies()
end

function pcie_device_app:register_reboot_method()
    -- 注册平滑重启回调函数
    -- 准备重启回调
    local SUCCESS<const> = 0
    local FAIL<const> = -1
    self:register_reboot_method()
    reboot_manage.on_prepare(function()
        return SUCCESS
    end)
    -- 执行重启回调
    reboot_manage.on_action(function()
        return SUCCESS
    end)
    -- 取消重启回调
    reboot_manage.on_cancel(function()
    end)
end

function pcie_device_app:register_rpc()
    local SYSTEM_ID_DEFAULT<const> = 1
    self:CreatePCIeDevices(SYSTEM_ID_DEFAULT)
    -- FDM根据错误类型设置不同错误事件属性状态
    self:ImplPCIeDevicesPCIeDevicesSetPcieErrorStatus(function(obj, ctx, ...)
        return self.device_service:method_set_error_status(ctx, ...)
    end)
    -- FDM设置不可纠正错误标识
    self:ImplPCIeDevicesPCIeDevicesSetUCEByBIOS(function(obj, ctx, ...)
        return self.device_service:method_set_uce_by_bios(ctx, ...)
    end)
    -- FDM设置降带宽事件标识
    self:ImplPCIeDevicesPCIeDevicesSetBandwidthReduction(function(obj, ctx, ...)
        return self.device_service:method_set_bandwidth_reduction(ctx, ...)
    end)
    -- FDM设置链路降速事件标识
    self:ImplPCIeDevicesPCIeDevicesSetLinkSpeedReduced(function(obj, ctx, ...)
        return self.device_service:method_set_link_speed_reduced(ctx, ...)
    end)
    -- FDM设置严重故障告警标识
    self:ImplPCIeDevicesPCIeDevicesSetDiagnosticFault(function (obj, ctx, ...)
        local status = self.device_service:method_set_diagnostic_fault(ctx, ...)
        return status
    end)
    -- FDM设置预故障事件标识
    self:ImplPCIeDevicesPCIeDevicesSetPredictiveFault(function (obj, ctx, ...)
        local status = self.device_service:method_set_predictive_fault(ctx, ...)
        return status
    end)
    -- PCIe设备丝印信息获取
    self:ImplPCIeDevicesPCIeDevicesGetDeviceName(function (obj, ctx, ...)
        local status, device_name = self.device_service:method_get_device_name(ctx, ...)
        return status, device_name
    end)
    -- 获取PCIeCard数量
    self:ImplPCIeDevicesPCIeDevicesGetPCIeCardNum(function (obj, ctx, ...)
        local num = self.device_service:method_get_pcie_card_num()
        return num
    end)
    -- 设置FaultByBios属性值
    self:ImplPCIeDevicesPCIeDevicesSetFaultByBios(function (obj, ctx, ...)
        local status = self.device_service:set_fault_status_by_bios(ctx, ...)
        return status
    end)
    -- PCIeSlot电源控制
    self:ImplPCIeSlotPCIeSlotPowerControl(function (obj, ctx, ...)
        return self.device_service:set_pcie_slot_power_state(obj, ctx, ...)
    end)
end

function pcie_device_app:register_ipmi()
    self:register_ipmi_cmd(app_ipmi.SetCpuBusSize, function(...)
        return self:SetCpuBusSize(...)
    end)
    self:register_ipmi_cmd(app_ipmi.UpdateIntegratedPcieConfig, function(...)
        return self:UpdateIntegratedPcieConfig(...)
    end)
    self:register_ipmi_cmd(app_ipmi.GetDeviceLocationName, function(...)
        return self:get_device_location_name(...)
    end)

    self:register_ipmi_cmd(app_ipmi.WritePCIeVPDInfoToBmc, function(...)
        return self:write_pcie_vpd_info(...)
    end)

    -- mezz pcie nic raid扣卡获取丝印信息一致
    self:register_ipmi_cmd(app_ipmi.GetMezzSilkName, function(...)
        return self:get_device_silk(def.com_type.MEZZ_CARD, msg.GetMezzSilkNameRsp, ...)
    end)
    self:register_ipmi_cmd(app_ipmi.GetPcieSilkName, function(...)
        return self:get_device_silk(def.com_type.PCIE_CARD, msg.GetPcieSilkNameRsp, ...)
    end)
    self:register_ipmi_cmd(app_ipmi.GetNicSilkName, function(...)
        return self:get_device_silk(def.com_type.NIC_CARD, msg.GetNicSilkNameRsp, ...)
    end)
    self:register_ipmi_cmd(app_ipmi.GetRaidSilkName, function(...)
        return self:get_device_silk(def.com_type.RAID_CARD, msg.GetRaidSilkNameRsp, ...)
    end)
    self:register_ipmi_cmd(app_ipmi.GetPCIeCardModel, function(...)
        return self:get_device_model(msg.GetPCIeCardModelRsp, ...)
    end)
    self:register_ipmi_cmd(app_ipmi.GetPCIeDevPresentStatus, function(...)
        return self:get_device_present_status(msg.GetPCIeDevPresentStatusRsp, ...)
    end)
    self:register_ipmi_cmd(app_ipmi.SetPCIeCardBDFs, function(...)
        return self:set_device_bdfs(msg.SetPCIeCardBDFsRsp, def.device_type.PCIE_CARD, ...)
    end)
    self:register_ipmi_cmd(app_ipmi.SetPCIeDiskBDFs, function(...)
        return self:set_device_bdfs(msg.SetPCIeDiskBDFsRsp, def.device_type.DISK, ...)
    end)
    self:register_ipmi_cmd(app_ipmi.SetOCPCardBDFs, function(...)
        return self:set_device_bdfs(msg.SetOCPCardBDFsRsp, def.device_type.OCP_CARD, ...)
    end)
end

local function get_npu_objects()
    local npu_list = {}
    for _ = 1, 3 do
        -- 框架默认重试10次，无npu卡时会超时，此处改为重试3次
        npu_list = get_non_virtual_interface_objects(client:get_bus(), 'bmc.kepler.Systems.Processor.NPU', false)
        if npu_list and next(npu_list) then
            break
        end
        skynet.sleep(50)
    end
    return npu_list
end

-- 通过bdf找npu对象路径
function pcie_device_app:get_npu_path_by_bdf(bus, device, func)
    local ok, root_bdf = pcall(string.format, '0000:%02x:%02x.%01x', bus, device, func)
    if ok then
        local npu_list = get_npu_objects()
        for _, o in pairs(npu_list) do
            if o.RootBDF == root_bdf then
                return o.path
            end
        end
    end
    log:debug('NPU get root_bdf failed, root_bdf=%s', root_bdf)
    return nil
end

function pcie_device_app:get_npu_silk(bus, device, func, device_type_parameter)
    -- npu的设备类型是pcie card
    if device_type_parameter ~=  def.com_type.PCIE_CARD then
        return nil
    end
    local npu_path = self:get_npu_path_by_bdf(bus, device, func)
    if npu_path then
        local ok, npu_obj = pcall(mdb.get_object, self.bus, npu_path, 'bmc.kepler.Systems.Processor')
        if ok then
            return npu_obj.Name
        end
    end
    return nil
end

function pcie_device_app:get_npu_device_location(bus, device, func, device_type_parameter)
    -- npu的设备类型是pcie card
    if device_type_parameter ~=  def.com_type.PCIE_CARD then
        return nil
    end
    local npu_path = self:get_npu_path_by_bdf(bus, device, func)
    if npu_path then
        local ok, npu_obj = pcall(mdb.get_object, self.bus, npu_path, 'bmc.kepler.Systems.Processor')
        if ok then
            return string.pack('s1s1', npu_obj.Name, npu_obj.Position)
        end
    end
    return nil
end

function pcie_device_app:get_device_silk(device_type_parameter, rsp_body, req)
    -- 通过BDF进行卡丝印查询
    local _, b, d, f = string.match(req.Data, '^(%x+):(%x+):(%x+).(%x+)$')
    if not b or not d or not f then
        log:error('ipmi convert pcie device bdf info failed')
        return rsp_body.new(cc.CommandNotAvailable, MANUFACTURE_ID, '')
    end

    local b_num = tonumber(b, 16)
    local d_num = tonumber(d, 16)
    local f_num = tonumber(f, 16)

    local pcie_device = nil
    for _, device in ipairs(self.device_service.pcie_device_list) do
        log:debug('device_type_parameter = %s, DeviceType=%s DeviceName=%s', device_type_parameter,
            device.mds.DeviceType, device.mds.DeviceName)

        if device.mds.DeviceType == device_type_parameter and device.mds.Bus == b_num and
            device.mds.Device == d_num and device.mds.Function == f_num then
            pcie_device = device.mds
            break
        end
    end

    -- 尝试找npu name
    local npu_name = self:get_npu_silk(b_num, d_num, f_num, device_type_parameter)
    if npu_name then
        log:debug('get device silk for b=%s d=%s f=%s. silk=%s', b_num, d_num, f_num,
            npu_name)
        return rsp_body.new(cc.Success, MANUFACTURE_ID, npu_name)
    end

    if not pcie_device then
        log:info('get device silk for b=%s d=%s f=%s failed for invalid bdf', b_num, d_num, f_num)
        return rsp_body.new(cc.CommandNotAvailable, MANUFACTURE_ID, '')
    end
    log:debug('get device silk for b=%s d=%s f=%s. silk=%s', b_num, d_num, f_num,
        pcie_device.DeviceName)
    return rsp_body.new(cc.Success, MANUFACTURE_ID, pcie_device.DeviceName)
end

function pcie_device_app:get_device_model(rsp_body, req)
    -- 通过BDF进行卡Model查询
    local _, b, d, f = string.match(req.Data, '^(%x+):(%x+):(%x+).(%x+)$')
    if not b or not d or not f then
        log:debug('ipmi convert pcie device bdf info failed')
        return rsp_body.new(cc.ParmOutOfRange, MANUFACTURE_ID, '')
    end

    local b_num = tonumber(b, 16)
    local d_num = tonumber(d, 16)
    local f_num = tonumber(f, 16)
    log:debug('converted rootBDF: %s:%s.%s', b_num, d_num, f_num)
    local pcie_device = nil
    for _, device in ipairs(self.device_service.pcie_device_list) do
        if device.mds.Bus == b_num and device.mds.Device == d_num and device.mds.Function == f_num then
            pcie_device = device.mds
            break
        end
    end

    if not pcie_device then
        return rsp_body.new(cc.ParmOutOfRange, MANUFACTURE_ID, '')
    end

    local pcie_card = nil
    for _, card in ipairs(self.device_service.pcie_card_list) do
        if card.mds.DeviceName == pcie_device.DeviceName then
            pcie_card = card.mds
            break
        end
    end

    if not pcie_card then
        log:info('get pcie device model for b=%s d=%s f=%s failed for invalid bdf', b_num, d_num, f_num)
        return msg.GetPCIeCardModelRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID, '')
    end
    log:debug('get pcie card location for b=%s d=%s f=%s. model=%s', b_num, d_num, f_num, pcie_card.Model)
    return msg.GetPCIeCardModelRsp.new(cc.Success, MANUFACTURE_ID, pcie_card.Model)
end

-- 支持通过BDF查询PCIE卡、网卡的名称及location
function pcie_device_app:get_device_location_name(req)
    -- 根据DeviceTypeParameter过滤
    local device_type_parameter = req.DeviceType

    -- 通过BDF进行卡丝印查询
    local _, b, d, f = string.match(req.InfoData, '^(%x+):(%x+):(%x+).(%x+)$')
    if not b or not d or not f then
        log:error('ipmi convert pcie device bdf info failed')
        return msg.GetDeviceLocationNameRsp.new(cc.CommandNotAvailable, MANUFACTURE_ID, '')
    end

    local b_num = tonumber(b, 16)
    local d_num = tonumber(d, 16)
    local f_num = tonumber(f, 16)

    local pcie_device = nil
    for _, device in ipairs(self.device_service.pcie_device_list) do
        log:debug('device_type_parameter = %s, DeviceType=%s DeviceName=%s', device_type_parameter,
            device.mds.DeviceType, device.mds.DeviceName)

        if device.mds.DeviceType == device_type_parameter and device.mds.Bus == b_num and 
            device.mds.Device == d_num and device.mds.Function == f_num then
            pcie_device = device.mds
            break
        end
    end

    -- 尝试找npu device location
    local npu_dev_loc = self:get_npu_device_location(b_num, d_num, f_num, device_type_parameter)
    if npu_dev_loc then
        log:debug('get device location for b=%s d=%s f=%s. dev_loc=%s', b_num, d_num, f_num,
            npu_dev_loc)
        return msg.GetDeviceLocationNameRsp.new(cc.Success, MANUFACTURE_ID, npu_dev_loc)
    end

    if not pcie_device then
        return msg.GetDeviceLocationNameRsp.new(cc.CommandNotAvailable, MANUFACTURE_ID, '')
    end

    local pcie_card = nil
    for _, card in ipairs(self.device_service.pcie_card_list) do
        if card.mds.DeviceName == pcie_device.DeviceName then
            pcie_card = card.mds
            break
        end
    end

    if not pcie_card then
        log:info('get pcie device location for b=%s d=%s f=%s. name=%s, position=%s', b_num, d_num,
            f_num, pcie_device.DeviceName, pcie_device.Position)
        return msg.GetDeviceLocationNameRsp.new(cc.Success, MANUFACTURE_ID, string.pack('s1s1',
            pcie_device.DeviceName, pcie_device.Position))
    end
    local location = pcie_card.Position ~= '' and pcie_card.Position or pcie_device.Position
    log:debug('get pcie card location for b=%s d=%s f=%s. name=%s, position=%s', b_num, d_num,
        f_num, pcie_device.DeviceName, location)
    return msg.GetDeviceLocationNameRsp.new(cc.Success, MANUFACTURE_ID,
        string.pack('s1s1', pcie_card.Name, location))
end

function pcie_device_app:get_device_present_status(rsp_body, req)
    local pcie_device_present = self.biz_topo_service:get_device_present_status(req)
    if not pcie_device_present then
        log:info('get pcie device present failed')
        return rsp_body.new(cc.InvalidFieldRequest, req.ManufactureId, 0)
    end
    
    log:debug('get pcie device type=%s present=%s', req.DeviceType, pcie_device_present)
    return rsp_body.new(cc.Success, req.ManufactureId, pcie_device_present)
end

-- 指令用于替代之前由Bios承载的上报所有BDF的指令
-- 分为三次上报，分别上报所有的OCP卡、Disk、PCIeDevice的BDF
-- 和之前的指令不会同时发出，保留旧有指令用于兼容
function pcie_device_app:set_device_bdfs(rsp_body, device_type, req, ctx)
    -- 从指令上下文中获取host_id
    local host_id = ctx and ctx.HostId or 1
    
    -- 解析指令
    local bdfs_data_pattern = bs.new([[<<
        SlotId:1/unit:8,
        SegmentNumber:2/unit:8,
        LogicProcessorId:1/unit:8,
        BusNumber:1/unit:8,
        DeviceNumber:1/unit:8,
        FunctionNumber:1/unit:8
    >>]])

    local card_infos = {}

    if #req.Data % req.Length ~= 0 then
        log:error('[BizTopoService] Worng ipmi data length')
        return rsp_body.new(cc.DataNotAvailable, req.ManufactureId)
    end

    for i = 1, #req.Data, req.Length do
        card_infos[#card_infos + 1] = bdfs_data_pattern:unpack(req.Data:sub(i, i + req.Length - 1))
    end

    -- 添加BDF到待加载列表
    self.device_service:on_set_pcie_bdf(host_id, device_type, card_infos)

    -- 使用BDF加载PCIe设备
    local map_slot_id_to_ssbdf = {}

    for _, card_info in pairs(card_infos) do
        if card_info.LogicProcessorId == 0 then
            map_slot_id_to_ssbdf[card_info.SlotId] = {
                card_info.SegmentNumber,
                card_info.LogicProcessorId,
                card_info.BusNumber,
                card_info.DeviceNumber,
                card_info.FunctionNumber
            }
        end
    end

    self.biz_topo_service:set_device_present_by_map(host_id, device_type, map_slot_id_to_ssbdf)
    return rsp_body.new(cc.Success, req.ManufactureId)
end

function pcie_device_app:init()
    pcie_device_app.super.init(self)
    orm_object_manage.get_instance(self.db, self.bus):start()
    self:check_dependencies()
    -- 注册对象响应回调函数
    -- 添加对象回调
    object_manage.on_add_object(self.bus, function(class_name, object, position)
        self.biz_topo_service:on_add_object(class_name, object, position)
        self.device_service:on_add_object(class_name, object, position)
    end, function(object)
        if object.class_name == 'BusinessConnector' or object.class_name == 'UnitConfiguration' then
            log:notice("%s delay uptree", object.name)
            return false
        end
        return true
    end)
    -- 删除对象回调
    object_manage.on_delete_object(self.bus, function(class_name, object, position)
        self.biz_topo_service:on_delete_object(class_name, object, position)
        -- 对象卸载后需要删除，避免再加载重复添加 PCIeDevice/PCIeCard 实例
        self.device_service:on_delete_object(class_name, object, position)
    end)
    -- 添加对象完成回调
    object_manage.on_add_object_complete(self.bus, function(position)
        self.biz_topo_service:on_add_object_complete(position)
        self.device_service:on_add_object_complete(position)
    end)
    -- 删除对象完成回调
    object_manage.on_delete_object_complete(self.bus, function(position)
    end)

    -- RPC方法注册
    self:register_rpc()
    self:register_ipmi()

    intf_debug.on_dump(function(ctx, path)
        self.device_service:method_card_info_dump(ctx, path)
        self.biz_topo_service.biz_topo:cable_info_dump(path)
    end)
    -- 恢复出厂回调
    mdb_config_manage.on_recover(function(ctx)
        self.biz_topo_service.device_loader:on_recover_cb(ctx)
    end)
    self.device_mgmt = device_mgmt.new(self)

    --导入导出注册
    mdb_config_manage.on_import(function (ctx, config_data, import_type)
        config_manage.on_import(self.db, ctx, config_data, import_type)
    end)
    mdb_config_manage.on_export(function (ctx, export_type)
        return config_manage.on_export(self.db, ctx, export_type)
    end)

end

function pcie_device_app:SetCpuBusSize(req, ctx)
    return self.biz_topo_service:set_cpu_bus_size(req, ctx)
end

function pcie_device_app:write_pcie_vpd_info(req, ctx)
    return self.device_service:write_pcie_vpd_info(req, ctx)
end

function pcie_device_app:UpdateIntegratedPcieConfig(req, ctx)
    return self.biz_topo_service:update_integrated_pcie_config(req, ctx)
end

function pcie_device_app:main()
    self.device_service:main()
    self.biz_topo_service:main()
end

return pcie_device_app
